package com.fuchuang.seckillsystem.service.impl;

import com.fuchuang.seckillsystem.dto.KeyConstants;
import com.fuchuang.seckillsystem.dto.TimeConstants;
import com.fuchuang.seckillsystem.entity.Activity;
import com.fuchuang.seckillsystem.entity.Goods;
import com.fuchuang.seckillsystem.exception.GlobalException;
import com.fuchuang.seckillsystem.mapper.ActivityMapper;
import com.fuchuang.seckillsystem.mapper.GoodsMapper;
import com.fuchuang.seckillsystem.rocketmq.OrderProducer;
import com.fuchuang.seckillsystem.service.IGoodService;
import com.fuchuang.seckillsystem.service.ISeckillService;
import com.fuchuang.seckillsystem.utils.MyStringUtils;
import com.fuchuang.seckillsystem.vo.RespBeanEnum;
import com.fuchuang.seckillsystem.vo.SeckillMessage;
import com.fuchuang.seckillsystem.vo.SeckillVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class SeckillServiceImpl implements ISeckillService, InitializingBean {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    @Qualifier("stock")
    private DefaultRedisScript<Long> redisScript;
    
    @Autowired
    private IGoodService goodService;
    
    @Autowired
    private OrderProducer orderProducer;
    
    private static ConcurrentHashMap<String, Boolean> isEmpty = new ConcurrentHashMap<>();
    
    @Resource
    private ActivityMapper activityMapper;
    
    @Resource
    private GoodsMapper goodsMapper;
    
    @Override
    public void setIsEmpty(Integer activityId, Integer goodsId, Boolean flag) {
        isEmpty.put(KeyConstants.SECKILL_ACTIVITY + activityId + ":" + goodsId, flag);
    }
    
    @Override
    public void insertGoodsToRedis(Boolean isSeckill) {
        //查询开始时间晚于当前时间1个小时的秒杀活动
        long time = System.currentTimeMillis() + 60 * 60 * 1000;
        Date laterDate = new Date(time);
        Date currentDate = new Date();
        List<Activity> activityList = activityMapper.selectPreloadingActivity(laterDate, currentDate, isSeckill);
        if (!CollectionUtils.isEmpty(activityList)) {
            for (Activity activity : activityList) {
                //获取该活动的商品
                Integer activityId = activity.getId();
                List<Goods> goodsList = goodsMapper.queryGoodsByActivityId(activityId);
                if (!CollectionUtils.isEmpty(goodsList)) {
                    for (Goods goods : goodsList) {
                        //存放商品库存
                        redisTemplate.boundHashOps(KeyConstants.SECKILL_ACTIVITY + activityId).put(KeyConstants.SECKILL_GOODS_STOCK + goods.getId(), goods.getStock());
                        if (goods.getStock() > 0) {
                            isEmpty.put(KeyConstants.SECKILL_ACTIVITY + activityId + ":" + goods.getId(), false);
                        } else {
                            isEmpty.put(KeyConstants.SECKILL_ACTIVITY + activityId + ":" + goods.getId(), true);
                        }
                    }
                    //将秒杀活动信息也存入redis中
                    redisTemplate.boundHashOps(KeyConstants.SECKILL_ACTIVITY + activityId).put(KeyConstants.ACTIVITY + activityId, activity);
                    //设置过期时间
                    redisTemplate.boundHashOps(KeyConstants.SECKILL_ACTIVITY + activityId).expireAt(activity.getEndTime());
                }
            }
        }
    }
    
    @Override
    public void removeGoodsFromIsEmpty(List<Integer> goodsIds, Integer activityId) {
        //删除内存中遗留的数据
        for (Integer goodsId : goodsIds) {
            isEmpty.remove(KeyConstants.SECKILL_ACTIVITY + activityId + ":" + goodsId);
        }
    }
    
    @Override
    public void resetIsEmpty() {
        //防止内存溢出
        List<Activity> activities = activityMapper.selectAllActivity();
        for (Activity activity : activities) {
            Boolean hasKey = redisTemplate.hasKey(KeyConstants.SECKILL_ACTIVITY + activity.getId());
            if (hasKey != null && hasKey) {
                return;
            }
        }
        isEmpty.clear();
    }
    
    @Override
    public String getSeckillPath(Integer activityId, Integer goodsId, Integer userId, Integer status) {
        if (activityId == null || goodsId == null || userId == null) {
            throw new GlobalException(RespBeanEnum.PARAMS_ERROR);
        }
        //判断秒杀活动是否存在
        Activity activity = (Activity) redisTemplate.boundHashOps(KeyConstants.SECKILL_ACTIVITY + activityId).get(KeyConstants.ACTIVITY + activityId);
        if (activity == null) {
            throw new GlobalException(RespBeanEnum.ACTIVITY_NOT_EXIST_ERROR);
        }
        //判断秒杀活动是否开始
        if (activity.getStartTime().after(new Date())) {
            throw new GlobalException(RespBeanEnum.SECKILL_NOT_START);
        }

        //判断抢购的商品是否存在 由于采用单机部署，可以查看本地内存下维护的isEmpty集合
        Boolean flag = isEmpty.get(KeyConstants.SECKILL_ACTIVITY + activityId + ":" + goodsId);
        if (flag == null) {
            throw new GlobalException(RespBeanEnum.GOODS_NOT_EXIST_ERROR);
        }
        //如果是分布式部署，则查看redis中是否有该商品的库存
//        Boolean hasKey = redisTemplate.boundHashOps(KeyConstants.SECKILL_ACTIVITY + activityId).hasKey(KeyConstants.SECKILL_GOODS_STOCK + goodsId);
//        if(hasKey == null || !hasKey){
//            throw new GlobalException(RespBeanEnum.GOODS_NOT_EXIST_ERROR);
//        }

        //判断用户是否通过初筛有权购买此商品
        Goods goods = goodService.selectGoodsById(goodsId);
        //如果该商品是贷款商品，并且用户没有通过初筛
        if (goods.getType() == 0 && status == 0) {
            throw new GlobalException(RespBeanEnum.SECKILL_AUTH_ERROR);
        }
        String encrypt = UUID.randomUUID().toString() + System.currentTimeMillis();
        redisTemplate.opsForValue().set(KeyConstants.PATH + activityId + ":" + goodsId + ":" + userId, encrypt, TimeConstants.PATH, TimeUnit.MINUTES);
        return encrypt;
    }
    
    @Override
    public void seckill(Integer activityId, Integer goodsId, String path, Integer userId) {
        //参数校验
        if (activityId == null || goodsId == null || MyStringUtils.isBlank(path)) {
            throw new GlobalException(RespBeanEnum.PARAMS_ERROR);
        }
        //判断路径是否合法
        String redisPath = (String) redisTemplate.opsForValue().get(KeyConstants.PATH + activityId + ":" + goodsId + ":" + userId);
        if (!path.equals(redisPath)) {
            throw new GlobalException(RespBeanEnum.PATH_ERROR);
        }
        //判断jvm中商品库存是否为空
        Boolean jvmStock = isEmpty.get(KeyConstants.SECKILL_ACTIVITY + activityId + ":" + goodsId);
        if (jvmStock) {
            throw new GlobalException(RespBeanEnum.STOCK_ERROR);
        }
        //判断redis中是否存在已购买的标志
        Boolean notBuy = redisTemplate.boundHashOps(KeyConstants.SECKILL_ACTIVITY + activityId).putIfAbsent(KeyConstants.SECKILL_REPEAT + goodsId + ":" + userId, userId);
        if (!notBuy) {
            throw new GlobalException(RespBeanEnum.REPEAT_ERROR);
        }
//        //执行lua脚本预减库存
        List<String> keys = new ArrayList<>();
        keys.add(KeyConstants.SECKILL_ACTIVITY + activityId);
        keys.add(KeyConstants.SECKILL_GOODS_STOCK + goodsId);
        Long stock = redisTemplate.execute(redisScript, keys);
        if (stock < 1) {
            isEmpty.put(KeyConstants.SECKILL_ACTIVITY + activityId + ":" + goodsId, true);
            redisTemplate.boundHashOps(KeyConstants.SECKILL_ACTIVITY + activityId).delete(KeyConstants.SECKILL_REPEAT + goodsId + ":" + userId);
            throw new GlobalException(RespBeanEnum.STOCK_ERROR);
        }
        //采用rocketMq异步处理订单
        SeckillMessage seckillMessage = new SeckillMessage(activityId, userId, goodsId);
        orderProducer.sendCreateOrderMessage(seckillMessage);
    }
    
    @Override
    public Integer getResult(Integer activityId, Integer goodsId, Integer userId) {
        //到redis中查询是否有请购成功的标准
        Integer orderId = (Integer) redisTemplate.boundHashOps(KeyConstants.SECKILL_ACTIVITY + activityId).get(KeyConstants.SECKILL_SUCCESS + goodsId + ":" + userId);
        if (orderId != null) {
            // 秒杀成功返回订单id
            return orderId;
        }
        //如果没有订单信息，则查询是否有秒杀失败的标识
        Boolean isFailuer = redisTemplate.boundHashOps(KeyConstants.SECKILL_ACTIVITY + activityId).hasKey(KeyConstants.SECKILL_FAILURE + goodsId + ":" + userId);
        if (isFailuer) {
            //放回秒杀失败
            return -1;
        }
        //返回还在秒杀
        return 0;
    }
    
    @Override
    public Integer getStock(SeckillVo seckillVo) {
        if (seckillVo.getActivityId() != null) {
            //先判断预加载库存中是否存在
            Integer stock = (Integer) redisTemplate.boundHashOps(KeyConstants.SECKILL_ACTIVITY + seckillVo.getActivityId()).get(KeyConstants.SECKILL_GOODS_STOCK + seckillVo.getGoodsId());
            if (stock != null) {
                return stock;
            }
        }
        //如果预加载里面没有，说明活动还没开始，到redis中查询商品信息
        Goods goods = (Goods) redisTemplate.opsForValue().get(KeyConstants.SECKILL_GOODS_STOCK + seckillVo.getGoodsId());
        if (goods != null) {
            return goods.getStock();
        }
        //如果redis中没有，再到mysql中查询
        goods = goodsMapper.selectByPrimaryKey(seckillVo.getGoodsId());
        if (goods == null) {
            throw new GlobalException(RespBeanEnum.GOODS_NOT_EXIST_ERROR);
        }
        return goods.getStock();
    }
    
    @Override
    public void afterPropertiesSet() {
        //启动服务时，加载正在秒杀的活动
        insertGoodsToRedis(true);
    }
}
